import warnings
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import MinMaxScaler
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.decomposition import PCA
from sklearn.tree import DecisionTreeClassifier
from sklearn.externals import joblib

## 设置属性防止中文乱码
mpl.rcParams['font.sans-serif']=[u'simHei']
mpl.rcParams['axes.unicode_minus']=False

warnings.filterwarnings(action='ignore',category=FutureWarning)

def run():
    iris_feature_E = 'sepal length', 'sepal width', 'petal length', 'petal width'
    iris_feature_C = '花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度'
    iris_class = 'Iris-setosa', 'Iris-versicolor', 'Iris-virginica'

    # 读取数据
    path='../data/iris.data'
    datas=pd.read_csv(path,header=None)
    X=datas[list(range(4))] #获取X变量
    Y=pd.Categorical(datas[4]).codes #把Y转换成分类型的0,1,2

    #数据进行分割（训练数据和测试数据）
    X_train,X_test,Y_train,Y_test=train_test_split(X,Y,train_size=0.8,random_state=14)
    X_train1,X_test1,Y_train1,Y_test1=X_train,X_test,Y_train,Y_test
    Y_train1.astype(np.int)
    Y_test1.astype(np.int)

    # 数据标准化
    # StandardScaler (基于特征矩阵的列，将属性值转换至服从正态分布)
    # 标准化是依照特征矩阵的列处理数据，其通过求z-score的方法，将样本的特征值转换到同一量纲下
    # 常用与基于正态分布的算法，比如回归

    # 数据归一化
    # MinMaxScaler （区间缩放，基于最大最小值，将数据转换到0,1区间上的）
    # 提升模型收敛速度，提升模型精度
    # 常见用于神经网络

    # Normalizer （基于矩阵的行，将样本向量转换为单位向量）
    # 其目的在于样本向量在点乘运算或其他核函数计算相似性时，拥有统一的标准
    # 常见用于文本分类和聚类、logistic回归中也会使用，有效防止过拟合
    ss=MinMaxScaler()
    # 用标准化方法对数据进行处理并转换
    X_train1=ss.fit_transform(X_train1)
    X_test1=ss.transform(X_test1)

    # 特征选择：从已有的特征中选择出影响目标值最大的特征属性
    # 常用方法：{ 分类：F统计量、卡方系数，互信息mutual_info_classif
              # { 连续：皮尔逊相关系数 F统计量 互信息mutual_info_classif
    # SelectKBest（卡方系数）
    skb=SelectKBest(chi2,k=3) # 在当前的案例中，使用SelectKBest这个方法从4个原始的特征属性，选择出来3个
    # K默认为10
    # 如果指定了，那么就会返回你所想要的特征的个数
    X_train1=skb.fit_transform(X_train1,Y_train1)  # 训练并转换
    X_test1=skb.transform(X_test1)  # 转换
    select_index=skb.get_support(indices=True)

    # 降维：对于数据而言，如果特征属性比较多，在构建过程中，会比较复杂，这个时候考虑将多维（高维）映射到低维的数据
    # 常用的方法：
    # PCA：主成分分析（无监督）
    # LDA：线性判别分析（有监督）类内方差最小，人脸识别，通常先做一次pca

    pca=PCA(n_components=2)  # 构建一个pca对象，设置最终维度是2维
    # 这里是为了后面画图方便，所以将数据维度设置了2维，一般用默认不设置参数就可以
    X_train1=pca.fit_transform(X_train1)  # 训练并转换
    X_test1=pca.transform(X_test1)  # 转换

    # 模型的构建
    dtc=DecisionTreeClassifier(criterion='entropy',random_state=0)  # 另外也可选gini
    # 模型训练
    dtc.fit(X_train1,Y_train1)
    # 模型预测
    Y_predict=dtc.predict(X_test1)
    # 模型结果的评估
    Y_test_1=Y_test1.reshape(-1)
    result=(Y_test_1==Y_predict)
    print("准确率:%.2f%%" %(np.mean(result)*100))
    # 实际可通过参数获取
    print("Score:",dtc.score(X_test1,Y_test1))  # 准确率
    print("Classes:",dtc.classes_)
    # 画图
    N=100 # 横纵各采样多少个值
    x1_min=np.min((X_train1.T[0].min(),X_test1.T[0].min()))
    x1_max=np.max((X_train1.T[0].max(),X_test1.T[0].max()))
    x2_min=np.min((X_train1.T[1].min(),X_test1.T[1].min()))
    x2_max=np.max((X_train1.T[1].max(),X_test1.T[1].max()))

    t1=np.linspace(x1_min,x1_max,N)
    t2=np.linspace(x2_min,x2_max,N)
    x1,x2=np.meshgrid(t1,t2)  # 生成网格采样点
    x_show=np.dstack((x1.flat,x2.flat))[0]  # 测试点
    Y_predict1=dtc.predict(x_show)  # 预测
    Y_predict1=Y_predict1.reshape(x1.shape) # 使之与输入的形状相同

    # 画图
    plt_light=mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
    plt_dark=mpl.colors.ListedColormap(['g','r','b'])

    plt.figure(figsize=(8,6),facecolor='w')
    plt.pcolormesh(x1,x2,Y_predict1,cmap=plt_light)
    plt.scatter(X_test1.T[0],X_test1.T[1],c=Y_test1.ravel(),edgecolors='k',s=150,cmap=plt_dark,zorder=10,marker="*")  # 测试数据
    plt.scatter(X_train1.T[0],X_train1.T[1],c=Y_train1.ravel(),edgecolors='k',s=40,cmap=plt_dark)  # 全部数据,cmap=plt_dark

    plt.xlabel(u'特征属性1',fontsize=15)
    plt.ylabel(u'特征属性2',fontsize=15)
    plt.xlim(x1_min,x1_max)
    plt.ylim(x2_min,x2_max)
    plt.grid(True)
    plt.title(u'鸢尾花数据的决策树分类',fontsize=18)
    plt.show()

    # 参数优化
    pipline=Pipeline([
        ('mms',MinMaxScaler()),
        ('skb',SelectKBest(chi2)),
        ('pca',PCA()),
        ('decision',DecisionTreeClassifier())
    ])
    # 参数
    parameters={
        "skb__k":[1,2,3,4],
        "pca__n_components":[0.5,0.99], #设置为浮点数代表主成分方差所占最小比例的阈值
        "decision__criterion":['gini','entropy'],
        "decision__max_depth":[1,2,3,4,5,6,7,8,9,10]
    }
    # 数据
    X_train2,X_test2,Y_train2,Y_test2=X_train,X_test,Y_train,Y_test
    # 模型构建：通过网格交叉验证，寻找最优参数列表， param_grid可选参数列表，cv：进行几折交叉验证
    gscv=GridSearchCV(pipline,param_grid=parameters,cv=3)
    # 模型训练
    gscv.fit(X_train2,Y_train2)
    # 算法的最优解
    print("最优参数列表:", gscv.best_params_)
    print("score值：", gscv.best_score_)
    # 预测值
    Y_predict2=gscv.predict(X_test2)
    # 应用最优参数看效果
    mms_best=MinMaxScaler()
    skb_best=SelectKBest(chi2,k=3)
    pca_best=PCA(n_components=0.99)
    dtc1=DecisionTreeClassifier(criterion='gini',max_depth=4)
    # 构建模型并训练模型
    X_train3,X_test3,Y_train3,Y_test3=X_train,X_test,Y_train,Y_test
    X_train3=pca_best.fit_transform(skb_best.fit_transform(mms_best.fit_transform(X_train3),Y_train3))
    X_test3=pca_best.transform(skb_best.transform(mms_best.transform(X_test3)))
    dtc1.fit(X_train3,Y_train3)
    print("正确率:",dtc1.score(X_test3,Y_test3))

    # 基于原始数据前3列比较一下决策树在不同深度的情况下错误率
    X_train4,X_test4,Y_train4,Y_test4=train_test_split(X.iloc[:,:2],Y,train_size=0.7,random_state=14)
    depths=np.arange(1,15)
    err_list=[]
    for d in depths:
         dtc2=DecisionTreeClassifier(criterion='entropy',max_depth=d) # 仅设置了这二个参数，没有对数据进行特征选择和降维，所以跟前面得到的结果不同
         dtc2.fit(X_train4,Y_train4)
         score=dtc2.score(X_test4,Y_test4)
         err=1-score
         err_list.append(err)
         print("%d深度，正确率%.5f" % (d, score))

    ## 画图
    plt.figure(facecolor='w')
    plt.plot(depths,err_list,'ro-',lw=3)
    plt.xlabel(u'决策树深度',fontsize=16)
    plt.ylabel(u'错误率',fontsize=16)
    plt.grid(True)
    plt.title(u'决策树层次太多导致的拟合问题(欠拟合和过拟合)',fontsize=18)
    plt.show()

    # GridSearchCV 模型保存和加载
    gcsv_best=gscv.best_estimator_
    joblib.dump(gcsv_best,"../model/gcsv.model")
    gcsv_best1=joblib.load("../model/gcsv.model") # 直接加载模型就表示可以用了，不需要重新训练了
    print(gcsv_best1.predict(X_test2))
    print(Y_predict2)


run()